package com.googlecode.imanager.client;

import java.io.Serializable;

import com.googlecode.imanager.client.action.Action;
import com.googlecode.imanager.client.bridge.BridgeManager;
import com.googlecode.imanager.client.bridge.BridgeType;
import com.googlecode.imanager.client.bridge.IComponent;
import com.googlecode.imanager.client.bridge.IControl;
import com.googlecode.imanager.client.bridge.style.IChildThemeHandler;
import com.googlecode.imanager.client.bridge.style.IStyle;
import com.googlecode.imanager.client.event.KeyEvent;
import com.googlecode.imanager.client.event.MouseEvent;
import com.googlecode.imanager.client.event.MouseEventType;
import com.googlecode.imanager.client.event.PhaseEvent;
import com.googlecode.imanager.client.layout.LayoutManager;
import com.googlecode.imanager.client.layout.LayoutPosition;
import com.googlecode.imanager.client.style.Style;
import com.googlecode.imanager.client.style.listener.StyleAttribute;
import com.googlecode.imanager.client.style.listener.StyleWrapper;
import com.googlecode.imanager.client.style.listener.ThemeStyleListener;
import com.googlecode.imanager.client.util.Anchors;
import com.googlecode.imanager.client.util.ControlState;
import com.googlecode.imanager.client.util.Dimension;
import com.googlecode.imanager.client.util.Logger;
import com.googlecode.imanager.client.util.Utils;

/**
 * @author wellsantos@wellsantos.com
 * @since 0.01
 */
// TODO fazer todos eventos onXxx ser protected
public abstract class Control extends Display implements IControl, Serializable, Cloneable {

    private static final long serialVersionUID = 1L;
    
    private int state;
    private Action action;
    private String hint;
    private Boolean visible;
    private Boolean disabled;
    private Anchors anchors;
    private LayoutPosition layoutPosition;
    private ControlStyleWrapper style;
    
    private Container parent;
    transient IControl bridge;
    
    public Control() {
        this(100, 20);
    }
    
    protected Control(int width, int height) {
        super(width, height);
        style = new ControlStyleWrapper();
    }
    
    protected void onPhaseEvent(PhaseEvent e){
    }
    
    protected void onResized(Dimension clientSize){
        if (parent != null){
            parent.onChildResized(this, clientSize);
        }
    }
            
    public void onMouseEvent(MouseEvent e) {
        if (!e.isCancelBubble()
                && hasParent()){
            Document doc = getParent().getDocument();
            if (doc != null){
                doc.onMouseEvent(e);
            }
        }
        if (e.getTarget() == this){
            handleDefault(e);
        }
    }

    public void onKeyEvent(KeyEvent e){
        if (!e.isCancelBubble()
                && hasParent()){
            Document doc = getParent().getDocument();
            if (doc != null){
                doc.onKeyEvent(e);
            }
        }
        if (e.getTarget() == this){
            handleDefault(e);
        }
    }
    
    @Override
    public void onStateChanged(ControlState state) {
    }

    protected void onClick() {
        executeAction();
    }
    
    public void click(){
        getBridge().click();
    }
    
    protected void handleDefault(MouseEvent e) {
        if (e.getEventType() == MouseEventType.MOUSE_CLICK 
                && e.getClickCount() == 1
                && !e.isPreventDefault()){
            onClick();
        }
    }

    protected void handleDefault(KeyEvent e) {
    }

    public void executeAction(){
        if (getAction() != null){
            getAction().execute(this);
        }
    }
    
    protected void onBeforeSetParent(Container newParent) {
    }
 
    protected void onAfterSetParent(Container oldParent) {
    }
 
    // FIXME o programador pode tentar setar um filho meu como sendo meu pai
    public final void setParent(Container parent) {
        if (this.parent != parent){
            startUpdateLayout();
            try {
                onBeforeSetParent(parent);
                Container oldParent = this.parent;
                
                if (oldParent != null){
                    this.parent = null;
                    oldParent.getChildren().remove(this);
                    BridgeManager.getInstance().changeParent(this, null);
                    getBridge().setParent(null);
                    oldParent.onChildRemoved(this);
                    oldParent.doLayoutChildren();
                }
                
                this.parent = parent;
    
                if (parent != null){
                    parent.getChildren().add(this);
                    BridgeManager.getInstance().changeParent(this, parent);
                    getBridge().setParent(parent);
                    parent.onChildAdded(this);
                    parent.doLayoutChild(this, LayoutManager.PARENT_CHANGED);
                }

                onAfterSetParent(oldParent);
            } finally {
                endUpdateLayout();
            }
        }
    }
    
    public final Container getParent() {
        return parent;
    }
    
    public final boolean hasParent() {
        return parent != null;
    }

    public Dimension getParentSize(){
        if (parent != null){
            return parent.getSize();
        }
        return null;
    }

    public final Container getRootParent() {
        Container other = getParent();
        Container parent = null;
        
        while (other != null){
            parent = other;
            other = other.getParent();
        }
        
        return parent;
    }
    
    public final Document getDocument() {
        Control other = this;
        
        do {
            if (other instanceof Document){
                return (Document) other;
            }
            other = other.parent;
        } while (other != null);
        
        return null;
    }
    
    public final Document getRootDocument() {
        Control other = this;
        Control doc = null;
        
        do {
            if (other instanceof Document){
                doc = other;
            }
            other = other.parent;
        } while (other != null);
        
        return (Document) doc;
    }
    
    public final Application getApplication() {
        Document doc = getRootDocument();
        return doc == null ? null : doc.application;
    }
    
    public Action getAction() {
        return action;
    }

    public void setAction(Action action) {
        if (!Utils.isEquals(this.action, action)){
            this.action = action;
            getBridge().setAction(action);
        }
    }

    public String getHint() {
        return hint;
    }

    public void setHint(String hint) {
        if (!Utils.isEquals(this.hint, hint)){
            this.hint = hint;
            getBridge().setHint(hint);
        }
    }

    public Style getStyle() {
        return style.getStyle();
    }

    public void setStyle(Style style) {
        this.style.setStyle(style);
    }
    
    public void setVisible(Boolean visible) {
        if (!Utils.isEquals(this.visible, visible)){
            this.visible = visible;
            getBridge().setVisible(isVisible());
            if (parent != null){
                parent.doLayoutChild(this, LayoutManager.VISIBILITY_CHANGED);
            }
        }
    }

    public Boolean getVisible() {
        return visible;
    }

    public boolean isVisible() {
        return visible == null ? true : visible;
    }
    
    public final boolean isShowing(){
        Control other = this;
        Control root;
        
        do {
            if (!other.isVisible()){
                return false;
            }
            root = other;
            other = other.parent;
        } while (other != null);
        
        if (root instanceof Document
                && ((Document) root).application != null){
            return true;
        }
        
        return false;
    }

    public void setDisabled(Boolean disabled) {
        if (!Utils.isEquals(this.disabled, disabled)){
            this.disabled = disabled;
            getBridge().setDisabled(isDisabled());
        }
    }

    public Boolean getDisabled() {
        return disabled;
    }

    public boolean isDisabled() {
        return disabled == null ? false : disabled;
    }
    
    public void realign(){
        getBridge().realign();
    }
    
    public void refresh(){
        getBridge().refresh();
    }
    
    public final int getState(){
        return state;
    }
    
    protected final boolean addState(ControlState state){
        int flag = state.getStateFlag();
        if ((this.state & flag) == 0){
            this.state |= flag;
            getBridge().onStateChanged(state);
            onStateChanged(state);
            return true;
        }
        return false;
    }
    
    protected final boolean removeState(ControlState state){
        int flag = state.getStateFlag();        
        if ((this.state & flag) != 0){
            this.state ^= flag;
            getBridge().onStateChanged(state);
            onStateChanged(state);
            return true;
        }
        return false;
    }
    
    protected final boolean hasState(ControlState state){
        return (this.state & state.getStateFlag()) != 0;
    }
    
    @Override
    protected void boundsChanged() {
        getBridge().setBounds(getLeft(), getTop(), getWidth(), getHeight());
        onResized(getClientSize());
        if (parent != null){
            parent.onChildPositionChanged(this);
        }
    }

    @Override
    protected void dimensionChanged() {
        getBridge().setBounds(getLeft(), getTop(), getWidth(), getHeight());
        onResized(getClientSize());
    }

    @Override
    protected void positionChanged() {
        getBridge().setBounds(getLeft(), getTop(), getWidth(), getHeight());
        if (parent != null){
            parent.onChildPositionChanged(this);
        }
    }

    public int getInsetLeft(){
        return 0;
    }
    
    public int getInsetTop(){
        return 0;
    }
    
    public int getInsetRight(){
        return 0;
    }
    
    public int getInsetBottom(){
        return 0;
    }
    
    public final int getClientHeight() {
        return getHeight() + getInsetTop() + getInsetBottom();
    }

    public final int getClientWidth() {
        return getWidth() + getInsetLeft() + getInsetRight();
    }

    public final Dimension getClientSize(){
        return new Dimension(getClientWidth(), getClientHeight());
    }
    
    @Override
    public Anchors getAnchors() {
        if (anchors == null){
            anchors = Anchors.DEFAULT;
        }
        return anchors;
    }  

    public void setAnchors(Anchors anchors) {
        this.anchors = anchors;
    }

    public LayoutPosition getLayoutPosition() {
        return layoutPosition;
    }

    public void setLayoutPosition(LayoutPosition position) {
        if (!Utils.isEquals(this.layoutPosition, position)){
            this.layoutPosition = position;            
            if (parent != null){
                parent.doLayoutChild(this, LayoutManager.LAYOUT_POSITION_CHANGED);
            }
        }
    }

    @Override
    public void scrollToRectVisible(int left, int top, int width, int height) {
        getBridge().scrollToRectVisible(left, top, width, height);
    }
    
    public IStyle getComputedStyle(){
        return getBridge().getComputedStyle();
    }
    
    protected IControl getBridge() {
        return (IControl) lookupBridge();
    }
    
    public final BridgeType getImplementationType(){
        return bridge == null ? BridgeType.EMPTY : bridge.getImplementationType();
    }
    
    protected final IControl lookupBridge() {
        if (bridge == null){
            bridge = (IControl) BridgeManager.getInstance().lookupImplementation(this);
        }
        return bridge;
    }
    
    protected static final IComponent lookupBridge(Object reference) {
        return BridgeManager.getInstance().lookupImplementation(reference);
    }
    
    protected final Logger getLogger(Class<?> ownerClass){
        return BridgeManager.getInstance().getLogger(ownerClass);
    }
    
    ThemeStyleListener getThemeListener(){
        return (ThemeStyleListener) style;
    }

    @Override
    public final boolean equals(Object obj) {
        return obj == this;
    }

    @Override
    public final int hashCode() {
        return super.hashCode();
    }

    @Override
    public Control clone() {
        Control c = (Control) super.clone();

        c.bridge = null;
        c.parent = null;
        c.style = c.new ControlStyleWrapper();

        return c;
    }

    public Control cloneTree() {
        return clone();
    }
    
    
    /**
     * @author wellsantos@wellsantos.com
     * @since 0.01
     */
    protected class ControlStyleWrapper extends StyleWrapper implements ThemeStyleListener {
        
        private static final long serialVersionUID = 1L;

        public ControlStyleWrapper() {
            super();
        }

        @Override
        public Control getControl() {
            return Control.this;
        }

        @Override
        public String getControlClassName() {
            return Control.this.getClass().getName();
        }

        @Override
        public int getStateFlags() {
            return 0;
        }

        final IChildThemeHandler getThemeHandler() {
            return (IChildThemeHandler) lookupBridge();
        }

        @Override
        public <T> void onStyleAttributeChanged(Style style, StyleAttribute<T> attribute, T attributeValue) {
            getThemeHandler().onStyleAttributeChanged(this, attribute, attributeValue);
        }

        @Override
        public <E> void onStyleAttributeChanged(StyleWrapper styleWrapper, StyleAttribute<E> attribute, E attributeValue) {
            getThemeHandler().onStyleAttributeChanged(styleWrapper, attribute, attributeValue);
            if (Control.this instanceof Container){
                for (Control control : ((Container) Control.this).getChildren()){
                    control.getThemeListener().onStyleAttributeChanged(styleWrapper, attribute, attributeValue);
                }
            }
        }
        
        @Override
        public String toString() {
            return "StyleWrapper[control=" + Control.this + "]";
        }

    }

}